package com.conf4j.scaner;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;

import com.conf4j.PropInfo;
import com.conf4j.kit.StrKit;

/**
 * 配置扫描类
 * 
 * @author dingnate
 *
 */
public abstract class ConfigScaner {
	private Timer timer;
	private TimerTask task;
	protected int tryTimes = 5;
	private int scanFailTimes = 0;
	protected boolean running = false;
	protected long intervalSec;
	// key : id , value : version
	private final Map<String, String> preScan = new HashMap<String, String>();
	private final Map<String, String> curScan = new HashMap<String, String>();
	private ConcurrentHashMap<String, PropInfo> propInfoMap = new ConcurrentHashMap<String, PropInfo>();
	private final Properties props = new Properties();
	private Collection<IConfigScanerLinstener> listeners = new HashSet<IConfigScanerLinstener>();

	/**
	 * @param url
	 * @param intervalSec
	 */
	public ConfigScaner(long intervalSec) {
		this.intervalSec = intervalSec;
	}

	private void initProps() {
		String[] scanKeys = getScanKeys();
		for (String key : scanKeys) {
			PropInfo newPropInfo = getPropInfo(key);
			propInfoMap.put(key, newPropInfo);
			props.putAll(newPropInfo.getProperties());
		}
	}

	private void clearProps() {
		propInfoMap.clear();
		props.clear();
	}

	protected void putScan(String key, String version) {
		curScan.put(key, version);
	}

	public void working() {
		if (!scan()) {
			if (++scanFailTimes > tryTimes)
				clearProps();
			return;
		}
		if (scanFailTimes > tryTimes) {
			initProps();
		}
		scanFailTimes = 0;

		compare();

		preScan.clear();
		preScan.putAll(curScan);
		curScan.clear();
	}

	private void compare() {
		// 有文件 被修改 或者新增了
		for (String key : getChangedKeys()) {
			PropInfo newPropInfo = getPropInfo(key);
			PropInfo oldPropInfo = propInfoMap.put(key, newPropInfo);
			for (Entry<Object, Object> entry : newPropInfo.getProperties().entrySet()) {
				String propertyKey = entry.getKey().toString();
				String oldPropertyValue = oldPropInfo == null ? null : oldPropInfo.getString(propertyKey);
				String newPropertyValue = (String) entry.getValue();
				if (!StrKit.equals(oldPropertyValue, newPropertyValue)) {
					props.put(propertyKey, newPropertyValue);
					onChange(key, propertyKey.toString(), oldPropertyValue, newPropertyValue);
				}
			}
			if (oldPropInfo == null)
				continue;
			for (Entry<Object, Object> entry : oldPropInfo.getProperties().entrySet()) {
				String propertyKey = entry.getKey().toString();
				if (newPropInfo.getString(propertyKey) != null)
					continue;
				props.remove(propertyKey);
				String oldPropertyValue = (String) entry.getValue();
				onChange(key, propertyKey, oldPropertyValue, null);
			}
		}

		// 有文件被删除了
		for (String deleteId : getDeleteKeys()) {
			PropInfo propInfo = propInfoMap.get(deleteId);
			for (Object key : propInfo.getProperties().keySet()) {
				props.remove(key);
				onChange(deleteId, key.toString(), propInfo.getString(key), null);
			}
		}
	}

	/**
	 * 记录被删除的文件id
	 * 
	 * @return
	 */
	private List<String> getDeleteKeys() {
		List<String> deleteKeys = new ArrayList<String>();
		for (Map.Entry<String, String> entry : preScan.entrySet()) {
			if (curScan.get(entry.getKey()) == null) {
				deleteKeys.add(entry.getKey());
			}
		}
		return deleteKeys;
	}

	/**
	 * 记录被修改或者新增的文件ID
	 * 
	 * @return
	 */
	private List<String> getChangedKeys() {
		List<String> changedKeys = new ArrayList<String>();
		for (Map.Entry<String, String> entry : curScan.entrySet()) {
			String version = entry.getValue();
			if (preScan.get(entry.getKey()) == null) {
				//新添加的文件
				changedKeys.add(entry.getKey());
			} else if (!version.equals(preScan.get(entry.getKey()))) {
				//文件被修改了
				changedKeys.add(entry.getKey());
			}
		}
		return changedKeys;
	}

	public void start() {
		if (running)
			return;
		timer = new Timer("Opx-Config-Scaner", true);
		task = new TimerTask() {
			public void run() {
				working();
			}
		};
		timer.schedule(task, 0, 1000L * intervalSec);
		running = true;
	}

	public void stop() {
		if (timer == null || !running)
			return;
		task.cancel();
		timer.cancel();
		running = false;
	}


	private void onChange(String key, String propertyKey, String oldValue, String newValue) {
		for (IConfigScanerLinstener listener : listeners)
			listener.onChange(key, propertyKey, oldValue, newValue);
	};
	protected abstract boolean scan();

	protected abstract PropInfo getPropInfo(String key);

	protected abstract String[] getScanKeys();
	
	protected abstract String getLocalPath();

	/**
	 * @param listeners the listeners to set
	 */
	public final void addListener(IConfigScanerLinstener listener) {
		this.listeners.add(listener);
	}

	/**
	 * @return the props
	 */
	public final Properties getProps() {
		return props;
	}
}
